using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.ObjectBuilder;

namespace Unity.Extensions
{
    public class InterfaceInterceptionStrategy : BuilderStrategy
    {
        static NamedTypeBuildKey InterceptInterface(IBuilderContext context,
                                                    Type typeToBuild,
                                                    Type originalType,
                                                    IEnumerable<KeyValuePair<MethodBase, List<IInterceptionHandler>>> handlers,
                                                    ConstructorInfo ctor,
                                                    object[] ctorParams)
        {
            // Create a wrapper class which implements the interface
            typeToBuild = InterfaceInterceptor.WrapInterface(originalType);

            // Create an instance of the concrete class using the policy data
            object wrappedObject = ctor.Invoke(ctorParams);

            // Create the proxy that's used by the wrapper
            ILEmitProxy proxy = new ILEmitProxy(handlers);

            // Create a new policy which calls the proper constructor
            ConstructorInfo newConstructor = typeToBuild.GetConstructor(new[] { typeof(ILEmitProxy), originalType });

            SpecifiedConstructorSelectorPolicy newPolicy =
                new SpecifiedConstructorSelectorPolicy(newConstructor, new InjectionParameterValue[]
                                                                           {
                                                                               new InjectionParameter(proxy),
                                                                               new InjectionParameter(wrappedObject)
                                                                           });

            NamedTypeBuildKey key = new NamedTypeBuildKey(typeToBuild);
            context.Policies.Set<IConstructorSelectorPolicy>(newPolicy, key);

            // Return the key for building
            return key;
        }

        public override void PreBuildUp(IBuilderContext context)
        {
            IConstructorSelectorPolicy creationPolicy = context.Policies.Get<IConstructorSelectorPolicy>(context.BuildKey);
            IInterfaceInterceptionPolicy interceptionPolicy = context.Policies.Get<IInterfaceInterceptionPolicy>(context.BuildKey);
            Type typeToBuild;

            if (creationPolicy != null &&
                interceptionPolicy != null &&
                BuildKey.TryGetType(context.BuildKey, out typeToBuild))
            {
                SelectedConstructor ctor = creationPolicy.SelectConstructor(context);
                string[] keys = ctor.GetParameterKeys();
                int count = keys.GetLength(0);
                object[] ctorParams = new object[count];
                for (int idx = 0; idx < count; idx++)
                {
                    ctorParams[idx] = context.Policies.Get<IDependencyResolverPolicy>(keys[idx]).Resolve(context);
                }
                Type originalType;

                if (!BuildKey.TryGetType(context.OriginalBuildKey, out originalType))
                    originalType = typeToBuild;

                context.BuildKey = InterceptInterface(context, typeToBuild, originalType, interceptionPolicy, ctor.Constructor, ctorParams);
            }

            base.PreBuildUp(context);
        }
    }
}